/*
 * axioms.c
 *
 * Copyright (C) 2015 - kali
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
#include "include/types.h"
#include "include/lexer.h"
#include "include/axioms.h"

atom *begin(acons *args, nspace *n) {
	if (args)
	{
		atom *val = eval(args->car, n);
		if (args->cdr)
			return begin(lmd_ccons(args->cdr), n);
		return val;
	}
	return NULL;
}


atom *quit(acons *args, nspace *n) {
	exit(0);
}

atom *lambda(acons *args, nspace *n) {
	if (!args)
	{
		printf("lambda: bad syntax\n");
		return NULL;
	}
	
	if (!args->car || *(args->car) == TSYM || *(args->car) == TCONS)
		if (args->cdr && *(args->cdr) == TCONS)
			return lmd_newfun(args->car, lmd_ccons(args->cdr), n);		

	printf("lambda: bad syntax\n");
	return NULL;
}

atom *defhelp(asym *name, atom *what, nspace *n) {
	nspace *copy = malloc(sizeof(nspace));
	*copy = *n;//内容复制

	n->next = copy;
	n->name = name;
	n->link = what;
	return what;
}

atom *define(acons *args, nspace *n) {
	if (!args)
	{
		printf("define: bad syntax\n");
		return NULL;
	}
	atom *what;
	asym *name;
	
	switch (*(args->car))
	{
		case TSYM:
			name = lmd_csym(args->car);
			what = eval(lmd_ccons(args->cdr)->car, n);
			break;
		case TCONS:
			name = lmd_csym(lmd_ccons(args->car)->car);
			what = lambda(lmd_ccons(lmd_newcons(lmd_ccons(args->car)->cdr, args->cdr)), n);
			break;
		default:
			printf("#wrong#");
			break;
	}
	
	return defhelp(name, what, n);
}

atom *defmacro(acons *args, nspace *n) {
	asym *name = lmd_csym(lmd_ccons(args->car)->car);
	atom *what = lambda(lmd_ccons(lmd_newcons(lmd_ccons(args->car)->cdr, args->cdr)), n);
	*what = TMAC;
	return defhelp(name, what, n);
}

atom *quote(acons *args, nspace *n) {
	return args->car;
}

atom *quasiquote(acons *args, nspace *n) {
//	printf("qq: ");
	lmd_print(lmd_catom(args->car));
	printf("\n");
	
	if (!args)
		return NULL;
	
	if (*(args->car) != TCONS) {
		//printf("nonlist\n");
		return args->car;
	}

	args = lmd_ccons(args->car);
	
	if (lmd_eq(args->car, lmd_newsym("unquote")))
		return eval(lmd_ccons(args->cdr)->car, n);
	
	return lmd_newcons(quasiquote(lmd_ccons(lmd_newcons(args->car, NULL)), n),
		quasiquote(lmd_ccons(args->cdr), n));
		
		
		

	
	//return args->car;
}

atom *car(acons *args, nspace *n) {
	atom *t = eval(args->car, n);
	if (*t == TCONS)
		return lmd_ccons(t)->car;
	printf("car: not a pair\n");
	return NULL;
}

atom *cdr(acons *args, nspace *n) {
	atom *t = eval(args->car, n);
	if (*t == TCONS)
		return lmd_ccons(t)->cdr;
	printf("cdr: not a pair\n");
	return NULL;
}

int listphelp(atom *p) {
	if (!p)
		return 1;
	return (*p == TCONS) && listphelp(lmd_ccons(p)->cdr);
}

atom *listp(acons *args, nspace *n) {
	if (!args)
	{
		printf("list?: expects 1 argument\n");
		return NULL;
	}
	return lmd_newbool(listphelp(eval(args->car, n)));
}

int lengthhelp(acons *p) {
	if (!p)
		return 0;
	return 1 + lengthhelp(lmd_ccons(p->cdr));
}

atom *length(acons *args, nspace *n) {
	atom *what = eval(args->car, n);
	if (!listphelp(what))
	{
		printf("length: invalid list\n");
		return NULL;
	}
	return lmd_newint(lengthhelp(lmd_ccons(what)));
}

atom *cons(acons *args, nspace *n) {
	if (!args || lengthhelp(args) != 2)
	{
		printf("cons: expects 2 arguments\n");
		return NULL;
	}
	atom *car = eval(args->car, n), *cdr = eval(lmd_ccons(args->cdr)->car, n);
	return lmd_newcons(car, cdr);
}

atom *aif(acons *args, nspace *n) {
	if (!args || lengthhelp(args) != 3)
	{
		printf("if: expects 3 arguments\n");
		return NULL;
	}
	if(eval(args->car, n) != f)
		return eval(lmd_ccons(args->cdr)->car, n);
	else
		return eval(lmd_ccons(lmd_ccons(args->cdr)->cdr)->car, n);
}

atom *numeq(acons *args, nspace *n) {
	if (!args)
		return t;
	if (lengthhelp(args) >= 2)
	{

		aint 	*a = lmd_cint(eval(args->car, n)),
				*b = lmd_cint(eval(lmd_ccons(args->cdr)->car, n));
		if (a && b)
			if (lengthhelp(args) == 2)
				return lmd_newbool(a->i == b->i);
			else
				return lmd_newbool(a->i == b->i && numeq(lmd_ccons(args->cdr), n) == t);
		else
		{
			printf("=: not a number\n");
			return NULL;
		}
	}
	else
	{
		printf("=: expects at least 2 arguments\n");
		return NULL;
	}
}

atom *nullp(acons *args, nspace *n) {
	if (lengthhelp(args) == 1)
	{
		atom *what = eval(args->car, n);
		return what ? f : t;
	}
	printf("null?: expects 1 argument\n");
	return NULL;
}

atom *add(acons *args, nspace *n) {
	if (!args)
		return lmd_newint(0);

	aint *car = lmd_cint(eval(args->car, n));
	//printf("+: back\n");
	//printf("+: val is %i\n", car->i);
	aint *cdr = lmd_cint(add(lmd_ccons(args->cdr), n));
	
	if (car && cdr)
		return lmd_newint(car->i + cdr->i);
	else
	{
		printf("+: bad int\n");
		return NULL;
	}
}

atom *sub(acons *args, nspace *n) {
	if (!args)
	{
		printf("-: expects at least 1 argument\n");
		return NULL;
	}
	int len = lengthhelp(args);
	aint *car = lmd_cint(eval(args->car, n));
	
	if (car){
		if (len == 1)
			return lmd_newint(0 - car->i);
		else
			return lmd_newint(car->i - lmd_cint(add(lmd_ccons(args->cdr), n))->i);
	}
	printf("-: not a number\n");
	return NULL;
}

atom *mult(acons *args, nspace *n) {
	if (!args)
		return lmd_newint(1);

	aint *car = lmd_cint(eval(args->car, n));
	aint *cdr = lmd_cint(mult(lmd_ccons(args->cdr), n));
	
	if (car && cdr)
		return lmd_newint(car->i * cdr->i);
	else
	{
		printf("*: bad int\n");
		return NULL;
	}
}

int sethelp(nspace *search, asym *name, atom *link) {
	if (!search) return 0;
	if (lmd_eq(lmd_catom(name), lmd_catom(search->name)))
	{
		search->link = link;
		return 1;
	}
	return sethelp(search->next, name, link);
}

atom *set(acons *args, nspace *n) {
	if (!n)
	{
		printf("set!: unbound symbol\n");
		return NULL;
	}
	if (lengthhelp(args) != 2)
	{
		printf("set!: expects 2 arguments\n");
		return NULL;
	}
	
	asym *name = lmd_csym(args->car);
	atom *link = eval(lmd_ccons(args->cdr)->car, n);
	
	if (sethelp(n, name, link))
		return link;
	printf("set!: undefined symbol\n");
	return NULL;
}

atom *let(acons *args, nspace *n) {
	if (lengthhelp(args) < 2) {
		printf("let: expects at least 2 arguments\n");
		return NULL;
	}
	if (!listphelp(args->car))
	{
		printf("let: expects list as first argument\n");
		return NULL;
	}
	
	nspace *new = n;
	acons *bl = lmd_ccons(args->car), *c;
	
	while (bl)
		if ((c = lmd_ccons(bl->car)) && *(c->car) == TSYM && lengthhelp(c) == 2)
		{
			// legit list
			new = lmd_bind(new, lmd_csym(c->car), eval(lmd_ccons(c->cdr)->car, n));
			bl = lmd_ccons(bl->cdr);
		}
		else
			printf("let: invalid let pair\n");
	
	return begin(lmd_ccons(args->cdr), new);
}

atom *lets(acons *args, nspace *n) {
	if (lengthhelp(args) < 2) {
		printf("let*: expects at least 2 arguments\n");
		return NULL;
	}
	if (!listphelp(args->car))
	{
		printf("let*: expects list as first argument\n");
		return NULL;
	}
	
	nspace *new = n;
	acons *bl = lmd_ccons(args->car), *c;
	
	while (bl)
		if ((c = lmd_ccons(bl->car)) && *(c->car) == TSYM && lengthhelp(c) == 2)
		{
			// legit list
			new = lmd_bind(new, lmd_csym(c->car), eval(lmd_ccons(c->cdr)->car, new));
			bl = lmd_ccons(bl->cdr);
		}
		else
			printf("let*: invalid let pair\n");
	
	return begin(lmd_ccons(args->cdr), new);
}

atom *letrec(acons *args, nspace *n) {
	if (lengthhelp(args) < 2) {
		printf("letrec: expects at least 2 arguments\n");
		return NULL;
	}
	if (!listphelp(args->car))
	{
		printf("letrec: expects list as first argument\n");
		return NULL;
	}
	
	nspace *new = n;
	acons *bl = lmd_ccons(args->car), *c;
	
	while (bl)
		if ((c = lmd_ccons(bl->car)) && *(c->car) == TSYM && lengthhelp(c) == 2)
		{
			// legit list
			new = lmd_bind(new, lmd_csym(c->car), NULL);
			bl = lmd_ccons(bl->cdr);
		}
		else
		{
			printf("let*: invalid let pair\n");
			return NULL;
		}
	

	bl = lmd_ccons(args->car);
	
	while (bl)
	{
		c = lmd_ccons(bl->car);
		sethelp(new, lmd_csym(c->car), eval(lmd_ccons(c->cdr)->car, new));
		bl = lmd_ccons(bl->cdr);
	}

	return begin(lmd_ccons(args->cdr), new);
}

atom *sapply(acons *args, nspace *n) {
	
	afun *fn = lmd_cfun(eval(args->car, n));
	acons *fa = lmd_ccons(eval(lmd_ccons(args->cdr)->car, n));
	
	return apply(fn, fa, n);
	
	
	// return eval(newcons(eval(args->car, n), eval(ccons(args->cdr)->car, n)), n);
	
	//return eval(lmd_catom(args), n);
}

atom *or(acons *args, nspace *n) {
	if (!args)
		return f;
	atom *val = eval(args->car, n);
	if (val == f)
		return or(lmd_ccons(args->cdr), n);
	return val;
}

atom *and(acons *args, nspace *n) {
	if (!args)
		return t;
	atom *val = eval(args->car, n);
	if (val == t)
		return and(lmd_ccons(args->cdr), n);
	return val;
}



int equalhelp(atom *a, atom *b) {
	/*printf("eh a=");
	print(a);
	printf(", b=");
	print(b);
	printf("\n");*/
	
	if (a && b && *a == *b && *a == TCONS) {		
		return	equalhelp(lmd_ccons(a)->car, lmd_ccons(b)->car) &&
				equalhelp(lmd_ccons(a)->cdr, lmd_ccons(b)->cdr);
	}
	return lmd_eq(a, b);
}


atom *equal(acons *args, nspace *n) {
	if (lengthhelp(args) != 2) {
		printf("equal?: expected 2 arguments\n");
		return NULL;
	}
	
	return lmd_newbool(equalhelp(	eval(args->car, n),
								eval(lmd_ccons(args->cdr)->car, n)));
}

atom *sprint(acons *args, nspace *n) {
	//printf("sprint called\n");
	if (args)
	{
		//printf("lblb\n");
		lmd_print(eval(args->car, n));
		return sprint(lmd_ccons(args->cdr), n);
	}
	else
	{
		printf("\n");
	}
	return NULL;
}

/**

	thinking time
	
	basics for call/cc:
	- save stack
	- save registers
	



**/

char *cstack (char *bottom, char *top) {
//	printf("diff: %i\n", bottom-top);
	char *stack = malloc(bottom-top);
	memcpy(stack, bottom, top-bottom);
	return stack;
}
/*
atom *callcc(acons *args, nspace *n) {
	char tos;
	//atom *ret;
	
	if (lengthhelp(args) != 1) {
		printf("call/cc: expected 1 argument\n");
		return NULL;
	}	
	
	atom *arg = eval(args->car, n);
	
	if (*arg != TFUN) {
		printf("call/cc: argument must be a function\n");
		return NULL;
	}
	
	acont *c = newcont(&tos);
	// needs to be replaced
	int size = c->size;
	char *from = &tos, *to = c->stack;
	while (size--)
		*(to++) = *(from++);
	
	//memcpy(c->stack, &tos, c->size);
	
	if (setjmp(c->registers))
	{
		return contperm;
		printf("returning from slumber\n");
		printf("ret: ");
		print(contperm);
		printf("\n");
		//ret = contperm;
	}
	else
	{
	//	c->s = 1;
		//printf("initial return\n");
		return apply(cfun(arg), ccons(newcons(lmd_catom(c), NULL)), n);
	}
	
	//printf("got here\n");
	return contperm;
}
*/
atom *lt(acons *args, nspace *n) {
	aint *a = lmd_cint(eval(args->car, n));
	aint *b = lmd_cint(eval(lmd_ccons(args->cdr)->car, n));
	return lmd_newbool(a->i < b->i);
}

atom *not(acons *args, nspace *n) {
	atom *thing = eval(args->car, n);
	return lmd_newbool(thing == f);
	
}


atom *load(acons *args, nspace *n) {
	
	astring *name = lmd_cstring(args->car);
	
	loadfile(name->s, n);
	
	return NULL;
}

atom *realeq(acons *args, nspace *n) {
	atom *a = eval(args->car, n);
	atom *b = eval(lmd_ccons(args->cdr)->car, n);
	int result;
	//printf("computing result\n");
	result = lmd_eq(a, b);
	//printf("have result\n");
	
	return lmd_newbool(result);
}
atom *test(acons *args,nspace *n) {
	nspace *c =n;
	while(c->next) {
		printf("The name is %s \n",c->name->s);
		c=c->next;
	}
	return 0;
}
